/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.parsing;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.w3c.dom.CharacterData;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author Clinton Begin
 */
/**
 * 对org.w3c.dom.Node的包装 Xml节点
 */
public class XNode {

	// org.w3c.dom.Node
	private Node node;
	// 以下都是预先把信息都解析好，放到map等数据结构中（内存中）
	/**
	 * 节点名字
	 */
	private String name;
	/**
	 * 节点内容
	 */
	private String body;
	/**
	 * 节点属性
	 */
	private Properties attributes;
	/**
	 * 节点变量
	 */
	private Properties variables;
	/**
	 *  XPathParser解析器
	 */
	private XPathParser xpathParser;

	// 在构造时就把一些信息（属性，body）全部解析好，以便我们直接通过getter函数取得
	/**
	 * 使用解析器构造XNode对象，有点像是一个dom对象，只有一个对象，因为一个xml最好还是个一个dom树最好
	 * 
	 * @param xpathParser
	 *            xml解析器
	 * @param node
	 *            节点
	 * @param variables
	 *            属性节点
	 */
	public XNode(XPathParser xpathParser, Node node, Properties variables) {
		this.xpathParser = xpathParser;
		this.node = node;
		this.name = node.getNodeName();
		this.variables = variables;
		 //获取当前节点的所有属性
		this.attributes = parseAttributes(node);
		//获取当前节点的文本节点内容，当然获取到的数据是已经经过TokenHandler处理过的
		this.body = parseBody(node);
	}

	/**
	 * 构造一个信息的XNode节点（保存在内存中 ）
	 *
	 * @param node
	 *            jdk的一般普通节点，也就是document返回的Node对象，自己进行封装一下
	 * @return
	 */
	public XNode newXNode(Node node) {
		return new XNode(xpathParser, node, variables);
	}

	/**
	 * 获取XNode的父亲（双亲），还是使用原始的node的操作，之后转化成XNode
	 * 
	 * @return 父亲的XNode对象
	 */
	public XNode getParent() {
		// 调用Node.getParentNode,如果取到，包装一下，返回XNode
		Node parent = node.getParentNode();
		if (parent == null || !(parent instanceof Element)) {
			return null;
		} else {
			return new XNode(xpathParser, parent, variables);
		}
	}

	// 取得完全的path (a/b/c)

	/**
	 * 获取路径，是xml中的标签的路径
	 * 
	 * @return 路径
	 */
	public String getPath() {
		// 循环依次取得节点的父节点，然后倒序打印,也可以用一个堆栈实现
		StringBuilder builder = new StringBuilder();
		Node current = node;
		while (current != null && current instanceof Element) {
			if (current != node) {
				builder.insert(0, "/");
			}
			builder.insert(0, current.getNodeName());
			current = current.getParentNode();
		}
		return builder.toString();
	}

	// 取得标示符 ("resultMap[authorResult]")
	// XMLMapperBuilder.resultMapElement调用
	// <resultMap id="authorResult" type="Author">
	// <id property="id" column="author_id"/>
	// <result property="username" column="author_username"/>
	// <result property="password" column="author_password"/>
	// <result property="email" column="author_email"/>
	// <result property="bio" column="author_bio"/>
	// </resultMap>
	/**
	 * 得到当前dom对象的标识符
	 * 
	 * @return String
	 */
	public String getValueBasedIdentifier() {
		StringBuilder builder = new StringBuilder();
		XNode current = this;
		while (current != null) {
			if (current != this) {
				builder.insert(0, "_");
			}
			// 先拿id，拿不到再拿value,再拿不到拿property
			String value = current.getStringAttribute("id",
					current.getStringAttribute("value", current.getStringAttribute("property", null)));
			if (value != null) {
				value = value.replace('.', '_');
				builder.insert(0, "]");
				builder.insert(0, value);
				builder.insert(0, "[");
			}
			builder.insert(0, current.getName());
			current = current.getParent();
		}
		return builder.toString();
	}

	// 解析
	// 以下方法都是把XPathParser的方法再重复一遍
	/**
	 * 解析String类型
	 * 
	 * @param expression
	 *            表达式
	 * @return String
	 */
	public String evalString(String expression) {
		return xpathParser.evalString(node, expression);
	}

	/**
	 * 解析boolean类型
	 * 
	 * @param expression
	 *            表达式
	 * @return Boolean
	 */
	public Boolean evalBoolean(String expression) {
		return xpathParser.evalBoolean(node, expression);
	}

	/**
	 * 解析Double类型
	 * 
	 * @param expression
	 *            表达式
	 * @return Double
	 */
	public Double evalDouble(String expression) {
		return xpathParser.evalDouble(node, expression);
	}

	/**
	 * 解析List<xml节点>集合
	 * 
	 * @param expression
	 *            表达式
	 * @return List<XNode>
	 */
	public List<XNode> evalNodes(String expression) {
		return xpathParser.evalNodes(node, expression);
	}

	/**
	 * 解析xml节点 --根节点
	 * 
	 * @param expression
	 *            表达式
	 * @return XNode
	 */
	public XNode evalNode(String expression) {
		return xpathParser.evalNode(node, expression);
	}

	public Node getNode() {
		return node;
	}

	public String getName() {
		return name;
	}

	// 以下是一些getBody的方法
	/**
	 * 得到String类型的body
	 * 
	 * @return String
	 */

	public String getStringBody() {
		return getStringBody(null);
	}

	/**
	 * 得到String类型的body
	 * 
	 * @param def
	 *            默认值
	 * @return String
	 */
	public String getStringBody(String def) {
		if (body == null) {
			return def;
		} else {
			return body;
		}
	}

	/**
	 * 得到Boolean类型的Body
	 * 
	 * @return Boolean
	 */
	public Boolean getBooleanBody() {
		return getBooleanBody(null);
	}

	/**
	 * 得到Boolean类型的Body
	 * 
	 * @param def
	 *            默认值
	 * @return Boolean
	 */
	public Boolean getBooleanBody(Boolean def) {
		if (body == null) {
			return def;
		} else {
			return Boolean.valueOf(body);
		}
	}

	/**
	 * 得到Integer类型的Body
	 * 
	 * @return Integer
	 */
	public Integer getIntBody() {
		return getIntBody(null);
	}

	/**
	 * 得到Integer类型的Body
	 * 
	 * @param def
	 *            默认值
	 * @return Integer
	 */
	public Integer getIntBody(Integer def) {
		if (body == null) {
			return def;
		} else {
			return Integer.parseInt(body);
		}
	}

	/**
	 * 得到Long类型的Body
	 * 
	 * @return Long
	 */
	public Long getLongBody() {
		return getLongBody(null);
	}

	/**
	 * 得到Long类型的Body
	 * 
	 * @param def
	 *            默认值
	 * @return Long
	 */
	public Long getLongBody(Long def) {
		if (body == null) {
			return def;
		} else {
			return Long.parseLong(body);
		}
	}

	/**
	 * 得到Double类型的Body
	 * 
	 * @return Double
	 */
	public Double getDoubleBody() {
		return getDoubleBody(null);
	}

	/**
	 * 得到Double类型的Body
	 * 
	 * @param def
	 *            默认值
	 * @return Double
	 */
	public Double getDoubleBody(Double def) {
		if (body == null) {
			return def;
		} else {
			return Double.parseDouble(body);
		}
	}
	/**
	 * 得到Float类型的Float
	 * 
	 * @return Float
	 */
	public Float getFloatBody() {
		return getFloatBody(null);
	}
	/**
	 * 得到Float类型的Body
	 * 
	 * @param def
	 *            默认值
	 * @return Float
	 */
	public Float getFloatBody(Float def) {
		if (body == null) {
			return def;
		} else {
			return Float.parseFloat(body);
		}
	}

	// 以下是一些getAttribute的方法
	/**
	 * 得到指定类型的枚举类型的value值
	 * @param enumType 枚举类型
	 * @param name property文件中的key
	 * @return T
	 */
	public <T extends Enum<T>> T getEnumAttribute(Class<T> enumType, String name) {
		return getEnumAttribute(enumType, name, null);
	}
	/**
	 * 得到指定类型的枚举类型的value值
	 * @param enumType 枚举类型
	 * @param name property文件中的key
	 * @param def 默认值
	 * @return T
	 */
	public <T extends Enum<T>> T getEnumAttribute(Class<T> enumType, String name, T def) {
		String value = getStringAttribute(name);
		if (value == null) {
			return def;
		} else {
			return Enum.valueOf(enumType, value);
		}
	}

	/**
	 * 通过key得到String属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @return String
	 */
	public String getStringAttribute(String name) {
		return getStringAttribute(name, null);
	}

	/**
	 * 通过key得到String属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @param def
	 *            默认值
	 * @return String
	 */
	public String getStringAttribute(String name, String def) {
		String value = attributes.getProperty(name);
		if (value == null) {
			return def;
		} else {
			return value;
		}
	}

	/**
	 * 通过key得到Boolean属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @return Boolean
	 */
	public Boolean getBooleanAttribute(String name) {
		return getBooleanAttribute(name, null);
	}

	/**
	 * 通过key得到Boolean属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @param def
	 *            默认值
	 * @return Boolean
	 */
	public Boolean getBooleanAttribute(String name, Boolean def) {
		String value = attributes.getProperty(name);
		if (value == null) {
			return def;
		} else {
			return Boolean.valueOf(value);
		}
	}

	/**
	 * 通过key得到Integer属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @return Integer
	 */
	public Integer getIntAttribute(String name) {
		return getIntAttribute(name, null);
	}

	/**
	 * 通过key得到Integer属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @param def
	 *            默认值
	 * @return
	 */
	public Integer getIntAttribute(String name, Integer def) {
		String value = attributes.getProperty(name);
		if (value == null) {
			return def;
		} else {
			return Integer.parseInt(value);
		}
	}

	/**
	 * 通过key得到Long属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @return Long
	 */
	public Long getLongAttribute(String name) {
		return getLongAttribute(name, null);
	}

	/**
	 * 通过key得到Long属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @param def
	 *            默认值
	 * @return Long
	 */
	public Long getLongAttribute(String name, Long def) {
		String value = attributes.getProperty(name);
		if (value == null) {
			return def;
		} else {
			return Long.parseLong(value);
		}
	}

	/**
	 * 通过key得到Double属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @return Double
	 */
	public Double getDoubleAttribute(String name) {
		return getDoubleAttribute(name, null);
	}

	/**
	 * 通过key得到Double属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @param def
	 *            默认值
	 * @return Double
	 */
	public Double getDoubleAttribute(String name, Double def) {
		String value = attributes.getProperty(name);
		if (value == null) {
			return def;
		} else {
			return Double.parseDouble(value);
		}
	}

	/**
	 * 通过key得到Float属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @return Float
	 */
	public Float getFloatAttribute(String name) {
		return getFloatAttribute(name, null);
	}

	/**
	 * 通过key得到Float属性
	 * 
	 * @param name
	 *            property文件中的key
	 * @param def
	 *            默认值
	 * @return Float
	 */
	public Float getFloatAttribute(String name, Float def) {
		String value = attributes.getProperty(name);
		if (value == null) {
			return def;
		} else {
			return Float.parseFloat(value);
		}
	}

	/**
	 * 得到当前节点下的子节点集合
	 * 
	 * @return List<XNode>
	 */
	// 得到孩子，原理是调用Node.getChildNodes
	public List<XNode> getChildren() {
		List<XNode> children = new ArrayList<XNode>();
		NodeList nodeList = node.getChildNodes();
		if (nodeList != null) {
			for (int i = 0, n = nodeList.getLength(); i < n; i++) {
				Node node = nodeList.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					children.add(new XNode(xpathParser, node, variables));
				}
			}
		}
		return children;
	}

	/**
	 * 得到孩子后放入Properties中后，返回Properties
	 * 
	 * @return Properties
	 */
	// 得到孩子，返回Properties，孩子的格式肯定都有name,value属性
	public Properties getChildrenAsProperties() {
		Properties properties = new Properties();
		for (XNode child : getChildren()) {
			String name = child.getStringAttribute("name");
			String value = child.getStringAttribute("value");
			if (name != null && value != null) {
				properties.setProperty(name, value);
			}
		}
		return properties;
	}

	// 打印信息，为了调试用
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("<");
		builder.append(name);
		for (Map.Entry<Object, Object> entry : attributes.entrySet()) {
			builder.append(" ");
			builder.append(entry.getKey());
			builder.append("=\"");
			builder.append(entry.getValue());
			builder.append("\"");
		}
		List<XNode> children = getChildren();
		if (!children.isEmpty()) {
			builder.append(">\n");
			for (XNode node : children) {
				// 递归取得孩子的toString
				builder.append(node.toString());
			}
			builder.append("</");
			builder.append(name);
			builder.append(">");
		} else if (body != null) {
			builder.append(">");
			builder.append(body);
			builder.append("</");
			builder.append(name);
			builder.append(">");
		} else {
			builder.append("/>");
		}
		builder.append("\n");
		return builder.toString();
	}

	// 以下2个方法在构造时就解析
	/**
	 * 获取这个节点的属性，就是你配置在这个节点中的属性，不是text内容(XNode并没有采用一种继承的策略，而是一种组合的关系，这样子最好了)
	 *
	 * @param n
	 *            节点
	 * @return Properties
	 */
	private Properties parseAttributes(Node n) {
		Properties attributes = new Properties();
		// 得到节点的属性集
		NamedNodeMap attributeNodes = n.getAttributes();
		if (attributeNodes != null) {
			// 遍历
			for (int i = 0; i < attributeNodes.getLength(); i++) {
				// 当前属性
				Node attribute = attributeNodes.item(i);
				String value = PropertyParser.parse(attribute.getNodeValue(), variables);
				attributes.put(attribute.getNodeName(), value);
			}
		}
		return attributes;
	}

	/**
	 * 解析body属性
	 * 
	 * @param node
	 *            节点
	 * @return String
	 */
	private String parseBody(Node node) {
		// 如果该节点不是文本节点或者CDATA节点，取其子节点值
		String data = getBodyData(node);
		if (data == null) {
			NodeList children = node.getChildNodes();
			// 尽管这个for循环不是一个好的实现方式，因为
			// children.getLength()被执行了多次，但在mybatis的源代码经常出现
			for (int i = 0; i < children.getLength(); i++) {
				Node child = children.item(i);
				data = getBodyData(child);
				// 只要一个节点为文本节点或者CDATA节点,就结束循环。因而此时的body值只是node的第一个文本节点的内容
				if (data != null) {
					break;
				}
			}
		}
		return data;
	}

	/**
	 * 得到body中的数据
	 * 
	 * @param child
	 *            节点<子节点>
	 * @return String
	 */
	private String getBodyData(Node child) {
		// 如果这个节点是文本节点或者CDATA节点，就取节点的内容，然后用PropertyParser.parse（）处理下
		if (child.getNodeType() == Node.CDATA_SECTION_NODE || child.getNodeType() == Node.TEXT_NODE) {
			String data = ((CharacterData) child).getData();
			data = PropertyParser.parse(data, variables);
			return data;
		}
		return null;
	}

}